package org.mte.sak.xml;

import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;

import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.ProcessingInstruction;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaderSAX2Factory;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.transform.JDOMResult;
import org.jdom2.transform.JDOMSource;
import org.jdom2.transform.XSLTransformer;
import org.mte.sak.util.Timer;


/**
 * Usa JDOM API per gestire un documento XML normale o XML trasformato (XSLT).
 *
 * @author Marco Traversari
 * @version $Revision: 1.4 $ $Date: 2011-06-14 14:30:35 $
 *
 * @see <a href="http://www.jdom.org">http://www.jdom.org </a>
 */
public class XMLDocumentManager {

	public static final String XML_DATE_PATTERN = "yyyy-MM-dd";
	public static final String XML_DATE_TIME_PATTERN =  "yyyy-MM-dd'T'HH:mm:ss";
	public static final String ENCODING = "UTF-8";
	/**
	 * Default Date
	 */
	public static final String XML_DEFAULT_DATE = "1970-01-01";

	/**
	 * Definizione file xml
	 */
	public static final String DEF = "xml";

    /**
     * Estensione del file xml
     */
    public static final String EXT = "." + DEF;

    /**
     * format
     */
    protected Format format = Format.getCompactFormat();

    /**
     * doc
     */
    protected Document doc = null;

    /**
     * expandEmptyElements
     */
    protected boolean expandEmptyElements = true;

    /**
     * builder
     */
    private SAXBuilder builder = null;

    /**
     * outputter
     */
    private XMLOutputter outputter = null;

    /**
     * encoding
     */
    private String encoding = ENCODING;

    /**
     * indent
     */
    private String indent = "\t";

    /**
     * Nome del SAX Driver da usare per il parsing.
     */
    //protected String defaultSaxDriverClass = "org.apache.xerces.parsers.SAXParser";

    /**
     * Nome del SAX Driver default da usare per il parsing.
     */
    //protected String defaultSaxDriverClass = "javax.xml.parsers.SAXParser";

    /**
     * XMLDocumentManager
     */
    public XMLDocumentManager() {
        doc = new Document();
    }

    /**
     * XMLDocumentManager
     *
     * @param filePath
     * @param isClassPathResource
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(String filePath, boolean isClassPathResource)
            throws JDOMException, IOException {
        if (isClassPathResource) {
            InputStream in = getClass().getResourceAsStream(filePath);
            build(in, null);
        } else {
            build(filePath, null);
        }
    }

    /**
     * XMLDocumentManager
     *
     * @param filePath
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(String filePath) throws JDOMException,
            IOException {
        build(filePath, null);
    }

    /**
     * XMLDocumentManager
     *
     * @param in
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(InputStream in) throws JDOMException, IOException {
        build(in, null);
    }

    /**
     * XMLDocumentManager
     *
     * @param b
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(StringBuilder b) throws JDOMException,
            IOException {
        build(b, null);
    }

    /**
     * XMLDocumentManager
     *
     * @param filePath
     * @param encoding
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(String filePath, String encoding)
            throws JDOMException, IOException {
        this.encoding = encoding;
        build(filePath, null);
    }

    /**
     * XMLDocumentManager
     *
     * @param filePath
     * @param saxDriverClass
     * @param encoding
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(String filePath, String saxDriverClass,
            String encoding) throws JDOMException, IOException {
        this.encoding = encoding;
        build(filePath, saxDriverClass);
    }

    /**
     * XMLDocumentManager
     *
     * @param in
     * @param encoding
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(InputStream in, String encoding)
            throws JDOMException, IOException {
        this.encoding = encoding;
        build(in, null);
    }

    /**
     * XMLDocumentManager
     *
     * @param in
     * @param saxDriverClass
     * @param encoding
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(InputStream in, String saxDriverClass,
            String encoding) throws JDOMException, IOException {
        this.encoding = encoding;
        build(in, saxDriverClass);
    }

    /**
     * XMLDocumentManager
     *
     * @param b
     * @param encoding
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(StringBuilder b, String encoding)
            throws JDOMException, IOException {
        this.encoding = encoding;
        build(b, null);
    }

    /**
     * XMLDocumentManager
     *
     * @param b
     * @param saxDriverClass
     * @param encoding
     * @throws JDOMException
     * @throws IOException
     */
    public XMLDocumentManager(StringBuilder b, String saxDriverClass,
            String encoding) throws JDOMException, IOException {
        this.encoding = encoding;
        build(b, saxDriverClass);
    }

	/**
	 * getEncoding
	 * @return encoding
	 */
	public String getEncoding() {
		return encoding;
	}

    /**
     * setEncoding
     * @param encoding
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /**
     * setIndent
     * @param indent
     *            The indent to set.
     */
    public void setIndent(String indent) {
        this.indent = indent;
    }

    /**
     * setExpandEmptyElements
     *
     * @param expandEmptyElements
     */
    public void setExpandEmptyElements(boolean expandEmptyElements) {
        this.expandEmptyElements = expandEmptyElements;
    }

    /**
     * Restituisce una stringa rappresentante il documento.
     *
     * @return il documento XML
     */
    public String outputString() {
        initOutputter();
        return outputter.outputString(doc);
    }

    /**
     * Restituisce una stringa rappresentante l'elemento specificato.
     *
     * @return l'elemento
     */
    public String outputString(Element element) {
        initOutputter();
        return outputter.outputString(element);
    }

    /**
     * Stampa il documento allo stdout.
     *
     * @throws IOException
     */
    public void print() throws IOException {
        initOutputter();
        outputter.output(doc, System.out);
    }

    /**
     * Salva il documento al percorso specificato.
     *
     * @param filePath
     *            è il percorso del file dove viene salvato il documento.
     * @throws IOException
     *
     */
    public void save(String filePath) throws IOException {
        initOutputter();
        FileWriter writer = new FileWriter(filePath);
        outputter.output(doc, writer);
        writer.close();
    }

    /**
     * Stampa il documento al <code>OutputStream</code> specificato.
     *
     * @param out
     *            è l'oggetto <code>OutputStream</code> dove viene copiato il
     *            documento.
     * @throws IOException
     */
    public void printToOutputStream(OutputStream out) throws IOException {
        initOutputter();
        outputter.output(doc, out);
    }

    /**
     * Esegue la trasformazione XSLT del documento utilizzato il file XSL
     * specificato.
     *
     * @param inputStream è il canale del file XSL
     * @throws TransformerException
     */
    public void transform(InputStream inputStream) throws TransformerException {
    	transform(TransformerFactory.newInstance().newTransformer(new StreamSource(inputStream)));
    }

    /**
     * Esegue la trasformazione XSLT del documento utilizzato il file XSL
     * specificato.
     *
     * @param stylesheet
     *            è il file XSL usato per la transformazione.
     * @throws TransformerException
     */
    public void transform(String stylesheet) throws TransformerException {
    	transform(TransformerFactory.newInstance().newTransformer(new StreamSource(stylesheet)));
    }

    /**
     * transform
     * @param transformer
     * @throws TransformerException
     */
    private void transform(Transformer transformer) throws TransformerException {
        JDOMResult out = new JDOMResult();
        transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
        transformer.transform(new JDOMSource(doc), out);
        doc = out.getDocument();
    }

    /**
     * <p>Esegue la trasformazione XSLT del documento utilizzato il file XSL
     * specificato utilizzando il <i>trasfomer indicato</i></p>.
     *
     * <p> Es: transformerClass = "net.sf.saxon.TransformerFactoryImpl"</p>
     *
     * @param stylesheet
     * @param transformerClass
     * @throws JDOMException
     */
    public void transform(String stylesheet, String transformerClass) throws JDOMException {
        System.setProperty("javax.xml.transform.TransformerFactory", transformerClass);
        XSLTransformer transformer = new XSLTransformer(stylesheet);
        doc = transformer.transform(doc);
    }

    /**
     * Restituisce l'elemento root.
     *
     * @return Element
     */
    public Element getRootElement() {
        return doc.getRootElement();
    }

    /**
     * Imposta l'elemento root.
     *
     * @param rootElement
     */
    public void setRootElement(Element rootElement) {
        doc.setRootElement(rootElement);
    }

    /**
     * Imposta il riferimento allo <code>schema</code>
     *
     * @param schemaFile
     */
    public void setSchemaReference(String schemaFile) {
        getRootElement().setAttribute(
                "noNamespaceSchemaLocation",
                schemaFile,
                Namespace.getNamespace("xsi",
                        "http://www.w3.org/2001/XMLSchema-instance"));
    }

    /**
     * setXSLReference
     * @param xslFile
     */
	public void setXSLReference(String xslFile) {
    	  HashMap<String, String> piMap = new HashMap<String, String>(2);
    	  piMap.put( "type", "text/xsl" );
    	  piMap.put( "href", xslFile);
    	  ProcessingInstruction pi = new ProcessingInstruction( "xml-stylesheet", piMap);
    	  doc.getContent().add(0, pi);
    }

    /**
     * Restituisce una array di stringhe con i valori della lista dei figli del
     * nodo indicato.
     *
     * @param node
     *            è l'oggetto <code>Element</code> padre.
     * @return un array di stringhe con i valori degli elementi indicati.
     *
     */
	@SuppressWarnings("rawtypes")
    public String[] getArray(Element node) {
        String[] arrayString = null;
        if (node != null) {
            List children = node.getChildren();
            int size = children.size();
            arrayString = new String[size];
            if (size > 0) {
	            for (int i = 0; i < size; i++) {
	                arrayString[i] = ((Element) children.get(i)).getText();
	            }
            }
        }
        return arrayString;
    }

    /**
     * Restituisce una array di stringhe con i valori della lista dei figli del
     * nodo indicato.
     *
     * @param node è l'oggetto <code>Element</code> padre.
     * @return un array di stringhe con i valori degli elementi indicati.
     */
	@SuppressWarnings("rawtypes")
	public ArrayList<String> getArrayList(Element node) {
        ArrayList<String> arrayList = null;
        if (node != null) {
            Iterator iterator = node.getChildren().iterator();
            for (int i = 0; iterator.hasNext(); i++) {
            	if (i == 0) {
            		arrayList = new ArrayList<String>();
            	}
            	arrayList.add(((Element) iterator.next()).getText());
            }
        }
        return arrayList;
    }

    /**
     * Restituisce l'elemento della lista ricavato dai figli dell'elemento
     * indicato, corrispondente all'indice specificato.
     *
     * @param node
     * @param index
     * @return Element
     */
	@SuppressWarnings("rawtypes")
    public Element getIndexedElement(Element node, int index) {
        Element element = null;
        if (node != null) {
            List children = node.getChildren();
            if (children != null && index < children.size()) {
                element = (Element) children.get(index);
            }
        }
        return element;
    }

    /**
     * Restituisce l'elemento di una lista il cui attributo specificato ha come
     * valore, il valore indicato.
     *
     * @param node
     * @param attributeName
     * @param attributeValue
     * @return un oggetto di tipo <code>Element</code>
     */
	@SuppressWarnings("rawtypes")
    public Element getIndexedElement(Element node, String attributeName,
            String attributeValue) {
        Element element = null;
        boolean isFound = false;
        if (node != null) {
            Iterator iterator = node.getChildren().iterator();
            while (iterator.hasNext() && !isFound) {
            	element = (Element) iterator.next();
            	Attribute attribute = element.getAttribute(attributeName);
            	if (attribute != null) {
            		isFound = attribute.getValue().equals(attributeValue);
            	}
            }
        }
        element = (!isFound) ? null : element;
        return element;
    }

    /**
     * getDoc
     *
     * @return doc
     */
    public Document getDoc() {
        return doc;
    }

    /**
     * setDoc
     *
     * @param doc
     */
    public void setDoc(Document doc) {
        this.doc = doc;
    }

    /**
     * toString
     *
     * @return rappresentazione dell'oggetto in una stringa.
     */
    public String toString() {
        return doc.toString();
    }

    /**
     * Inizializza l'oggetto necessario per gestire l'output del documento.
     */
    private void initOutputter() {
        format.setEncoding(encoding);
        format.setIndent(indent);
        format.setExpandEmptyElements(expandEmptyElements);
        outputter = new XMLOutputter(format);
    }

    /**
     * build()
     *
     * @param xmlObject
     * @param saxDriverClass
     * @throws JDOMException
     * @throws IOException
     */
    private void build(Object xmlObject, String saxDriverClass)
            throws JDOMException, IOException {
        //builder = new SAXBuilder(saxDriverClass);
        builder = new SAXBuilder(new XMLReaderSAX2Factory(false, saxDriverClass));
        if (xmlObject instanceof String) {
            String strXmlObject = (String) xmlObject;
            doc = builder.build(strXmlObject);
        } else if (xmlObject instanceof InputStream) {
            InputStream inXmlObject = (InputStream) xmlObject;
            doc = builder.build(inXmlObject);
        } else if (xmlObject instanceof StringBuffer) {
            StringBuffer bfXmlObject = (StringBuffer) xmlObject;
            String strBuffer = bfXmlObject.toString();
            StringReader sr = new StringReader(strBuffer);
            doc = builder.build(sr);
        } else if (xmlObject instanceof StringBuilder) {
        	StringBuilder bfXmlObject = (StringBuilder) xmlObject;
            String strBuilder = bfXmlObject.toString();
            StringReader sr = new StringReader(strBuilder);
            doc = builder.build(sr);
        }
    }

    /**
     * Costruisce un documento leggendo da file.
     *
     * @param filePath
     * @throws JDOMException
     * @throws IOException
     */
    public void buildFromFile(String filePath) throws JDOMException,
            IOException {
        build(filePath, null);
    }


    /**
     * Costruisce un documento leggendo da un <code>StringBuilder</code>.
     *
     * @param b
     * @throws JDOMException
     * @throws IOException
     */
    public void buildFromStringBuilder(StringBuilder b)
    	throws JDOMException, IOException {
		build(b, null);
	}

    /**
     * Costruisce un documento leggendo da un oggetto di tipo
     * <code>InputStream</code>.
     *
     * @param in
     * @throws JDOMException
     * @throws IOException
     */
    public void buildFromInputStream(InputStream in) throws JDOMException,
            IOException {
        build(in, null);
    }

    /**
     * Converte il valore in <code>int</code>.
     *
     * @param value
     * @return un valore <code>int</code>
     * @deprecated use getInteger(String value);
     */
    public int getInt(String value) {
        return getInt(value, -1);
    }

    /**
     * Converte il valore in <code>int</code>, se non è nullo altrimenti
     * restituisce il valore di defualt specificato.
     *
     * @param value
     * @param intDefaultValue
     * @return int
     * @deprecated by getInteger(String value, int intDefaultValue);
     */
    public int getInt(String value, Integer intDefaultValue) {
        int intValue = -1;
        if (value != null && !value.equals("")) {
            intValue = Integer.parseInt(value);
        } else {
            intValue = intDefaultValue;
        }
        return intValue;
    }

    /**
     * getInteger
     * @param value
     * @return integer
     */
    public Integer getInteger(String value) {
        return getInteger(value, -1);
    }

    /**
     * getInteger
     * @param value
     * @param intDefaultValue
     * @return integer
     */
    public Integer getInteger(String value, Integer intDefaultValue) {
    	Integer integer = null;
    	 if (value != null && !value.equals("")) {
    		 integer = new Integer(value);
    	 } else {
    		 integer = intDefaultValue;
    	 }
        return integer;
    }

    /**
     * getDouble
     * @param value
     * @return double
     */
    public Double getDouble(String value) {
    	return getDouble(value, -1d);
    }

    /**
     * getDouble
     * @param value
     * @param defaultValue
     * @return double
     */
    public Double getDouble(String value, Double defaultValue) {
    	Double d = null;
    	 if (value != null && !value.equals("")) {
    	    d = new Double(value);
    	 } else {
    		 d = defaultValue;
    	 }
    	return d;
    }

    /**
     * getFloat
     * @param value
     * @return float
     */
    public Float getFloat(String value) {
    	return getFloat(value, -1f);
    }

    /**
     * getFloat
     * @param value
     * @param defaultValue
     * @return float
     */
    public Float getFloat(String value, Float defaultValue) {
    	Float d = null;
    	 if (value != null && !value.equals("")) {
    	    d = new Float(value);
    	 } else {
    		 d = defaultValue;
    	 }
    	return d;
    }

    /**
     * getDate
     * @param value
     * @param locale
     * @return date
     */
    public Date getDate(String value, Locale locale) {
    	return getDate(value, null, XML_DATE_PATTERN, locale);
    }

    /**
     * getDate
     * @param value
     * @param pattern
     * @param locale
     * @return date
     */
    public Date getDate(String value,
    		String pattern, Locale locale) {
    	return getDate(value, null, pattern, locale);
    }

    /**
     * getDate
     * @param value
     * @param defaultValue
     * @param pattern
     * @param locale
     * @return date
     */
    public Date getDate(String value, String defaultValue,
    		String pattern, Locale locale) {
    	Date date = null;
    	Timer timer = new Timer(pattern, locale);
    	if (value != null) {
    		try {
    			date = timer.getDate(value);
    		} catch (ParseException e) {
    			date = null;
    		}
    	}
    	if (date == null && defaultValue != null) {
    		try {
    			date = timer.getDate(defaultValue);
    		} catch (ParseException e) {
    			date = null;
    		}
    	}
    	return date;
    }

    /**
     * getDateTime
     * @param value
     * @param locale
     * @return date
     */
    public Date getDateTime(String value, Locale locale) {
    	return getDateTime(value, null, XML_DATE_TIME_PATTERN, locale);
    }

    /**
     * getDateTime
     * @param value
     * @param pattern
     * @param locale
     * @return date
     */
    public Date getDateTime(String value,
    		String pattern, Locale locale) {
    	return getDateTime(value, null, pattern, locale);
    }

    /**
     * getDateTime
     * @param value
     * @param defaultValue
     * @param pattern
     * @param locale
     * @return date
     */
    public Date getDateTime(String value, String defaultValue,
    		String pattern, Locale locale) {
    	Date date = null;
    	Timer timer = new Timer(pattern, locale);
    	if (value != null) {
    		try {
    			date = timer.getDate(value);
    		} catch (ParseException e) {
    			date = null;
    		}
    	}
    	if (date == null && defaultValue != null) {
    		try {
    			date = timer.getDate(defaultValue);
    		} catch (ParseException e) {
    			date = null;
    		}
    	}
    	return date;
    }

    /**
     * Converte il valore in <code>Boolean</code>.
     *
     * @param value
     * @return un valore <code>boolean</code>
     */
    public Boolean getBoolean(String value) {
        return new Boolean(value);
    }


    /**
     * Converte il valore in <code>Boolean</code>, se non è nullo altrimenti
     * restituisce il valore di default specificato.
     *
     * @param value
     * @param defaultBooleanValue
     * @return un tipo primitivo <code>boolean</code>.
     */
    public Boolean getBoolean(String value, Boolean defaultValue) {
    	Boolean bln = false;
    	if (value != null && !value.equals("")) {
    		bln = new Boolean(value);
    	} else {
    		bln = defaultValue;
    	}
    	return bln;
    }

    /**
     * Converte il valore in <code>String</code>.
     *
     * @param value
     * @return una stringa rappresentante il valore.
     */
    public String getString(String value) {
        return getString(value, null);
    }

    /**
     * Converte il valore in <code>String</code>, se non è nullo altrimenti
     * restituisce il valore di defualt specificato.
     *
     * @param value
     * @param defaultValue
     * @return una stringa rappresentante il valore.
     */
    public String getString(String value, String defaultValue) {
        if (value == null || value.equals("")) {
            value = defaultValue;
        }
        return value;
    }
}